/** \file    imglib/avi_read.cpp
  * \brief   AVI-streams reading facilities.
  * \date    2004
  * \author  Fedor Chelnokov
  * \version $Header: /home/cscom1r4/cvsroot/cs/imglib/src/avi_read.cpp,v 1.16 2005/09/17 16:35:01 cscom1r4 Exp $
  */

#include "imglib/avi.h"
#include <xtd/string>

#pragma comment(lib, "Vfw32.lib")

namespace img {

namespace avi {

footage::footage() 
{ 
	_file = 0;
	_stream = 0;
}

footage::footage(const std::string & filename) 
{ 
	_file = 0;
	_stream = 0;
	open(filename);
}

footage::~footage() 
{
	close();
}

void footage::open(const std::string & filename)
{
	close();

	if (AVIERR_OK != AVIFileOpen(&_file, filename.c_str(), OF_READ|OF_SHARE_DENY_WRITE, NULL))
		throw exception(std::string("error opening AVI (possibly it's not a valid AVI file): ") + filename);
	if (AVIERR_OK != AVIFileGetStream(_file, &_stream, streamtypeVIDEO, 0))
		throw exception(std::string("error getting main video stream of AVI: ") + filename);

	_duration = AVIStreamLength(_stream);
	if (_duration == -1)
		throw exception(std::string("error getting duration of main video stream in ") + filename);
}

void footage::close()
{
	if (_stream) {
		AVIStreamRelease(_stream);
		_stream = 0;
	}
	if (_file) {
		AVIFileRelease(_file);
		_file = 0;
	}
}

bool footage::all_keyframes() const
{
	assert(is_open());
	AVIFILEINFO info;
	if (AVIERR_OK != AVIFileInfo(_file, &info, sizeof(info)))
		throw exception("error getting AVI file info");
	return (info.dwCaps & AVIFILECAPS_ALLKEYFRAMES) != 0;
}

//reader

reader::reader(const footage & f, int frame) : _footage(f)
{
	assert(_footage.is_open());

	_next = frame+1;
	_next_key = -1;
	jump(frame);

	AVISTREAMINFO header;
	if (AVIERR_OK != AVIStreamInfo(hstream(), &header, sizeof(header)))
		throw exception("error getting AVI stream info");
	_fps = fraction(header.dwRate, header.dwScale);

	LONG dwInFormatSize;
	if (AVIERR_OK != AVIStreamFormatSize(hstream(), 0, &dwInFormatSize) || dwInFormatSize < 0)
		throw exception("error getting AVI stream format's size");
	_in_format.resize(dwInFormatSize);
	if (AVIERR_OK != AVIStreamReadFormat(hstream(), 0, in_format(), &dwInFormatSize))
		throw exception("error getting AVI stream format");

	_hic = ICLocate(ICTYPE_VIDEO, header.fccHandler, in_format(), 0, ICMODE_DECOMPRESS);
	if (_hic == 0) {
		union {
			DWORD fccHandler;
			char code[5];
		} id;
		id.fccHandler = header.fccHandler;
		id.code[4] = 0;
		throw exception(std::string("AVI decompressor \"") + id.code+ "\" not found in the system");
	}

	DWORD dwOutFormatSize = ICDecompressGetFormatSize(_hic, in_format());
	if ((int)dwOutFormatSize < 0)
		throw exception("error getting decompressed format's size");
	_out_format.resize(dwOutFormatSize);
	if (ICERR_OK != ICDecompressGetFormat(_hic, in_format(), out_format()))
		throw exception("error getting decompressed format");

	_im24.order(img::bottom_up);
	_im32.order(img::bottom_up);
	_dim = size_extent(out_format()->biWidth, out_format()->biHeight);

	if (out_format()->biBitCount != 32) {
		WORD biBitCount = 32;
		DWORD biSizeImage = out_format()->biWidth * out_format()->biHeight * 4;
		std::swap(biBitCount, out_format()->biBitCount);
		std::swap(biSizeImage, out_format()->biSizeImage);
		if (ICDecompressQuery(_hic, in_format(), out_format()) != ICERR_OK) {
			std::swap(biBitCount, out_format()->biBitCount);
			std::swap(biSizeImage, out_format()->biSizeImage);
			if (out_format()->biBitCount != 24)
				throw exception(xtd::string("unsupported bit count of AVI stream: ") += out_format()->biBitCount);
			_im24.resize(_dim);
		}
	}
	if (out_format()->biBitCount == 32)
		_im32.resize(_dim);

	DWORD e;
	if (ICERR_OK != (e=ICDecompressBegin(_hic, in_format(), out_format())))
		throw exception(std::string("video decompression begin error: ") + decode_icerr(e));
}

	//divx5 may handle both divx and xvid decoders
	/*ICINFO icinfo;
	HIC hic = 0;
	for (int i=0; ICInfo(ICTYPE_VIDEO, i, &icinfo); i++) 
	{ 
		HIC hic = ICOpen(ICTYPE_VIDEO, icinfo.fccHandler, ICMODE_QUERY); 
		if (hic) 
		{ 
			union {
				DWORD fccHandler;
				char code[5];
			} id;
			id.fccHandler = icinfo.fccHandler;
			id.code[4] = 0;
			std::cout << id.code << std::endl;
			if (ICDecompressQuery(hic, in_format(), NULL) == ICERR_OK) {
				std::cout << "^" << std::endl;
			}
			ICClose(hic); 
		} 
	}*/

reader::~reader()
{
	if (_hic) {
		ICDecompressEnd(_hic);
		ICClose(_hic);
	}
}

int reader::nearest_keyframe_to(int frame) const
{
	int n = AVIStreamNearestKeyFrame(hstream(), frame);
	if (-1 == n)
		throw exception(xtd::string("can't find in AVI stream start position to read frame #") += frame);
	return n;
}

void reader::jump(int frame)
{
	assert(frame >= 0 && frame < _footage.duration());
	if (frame >= _next && (frame < _next_key || _next_key == -1))
		return;
	if (_next_key == frame)
		_base_key = frame;
	else
		_base_key = nearest_keyframe_to(frame);
	_next = _base_key;
	_next_key = AVIStreamNextKeyFrame(hstream(), frame);
	assert(_base_key != _next_key);
}

void reader::read_next(bool nothrow)
{
	LONG size = 0;
	if (AVIStreamRead(hstream(), _next, 1, 0, 0, &size, 0) || size <= 0)
		throw exception(xtd::string("error reading from AVI stream sample size: ") += size);
	std::vector<char> buf(size);
	if (AVIStreamRead(hstream(), _next, 1, &buf[0], size, 0, 0))
		throw exception("error reading sample from AVI stream");

	DWORD e;
	if (native_32bit()) {
		e=ICDecompress(_hic, (_next == _base_key) ? 0 : ICDECOMPRESS_NOTKEYFRAME, 
			in_format(), &buf[0], out_format(), (char*)_im32.data());
		_valid24 = false;
		_valid32 = true;
	} 
	else  {
		e=ICDecompress(_hic, (_next == _base_key) ? 0 : ICDECOMPRESS_NOTKEYFRAME, 
			in_format(), &buf[0], out_format(), (char*)_im24.data());
		_valid24 = true;
		_valid32 = false;
	} 
	if (ICERR_OK != e) {
		if (nothrow) {
			if (native_32bit())
				memset(_im32.data(), 0, _im32.bytes());
			else
				memset(_im24.data(), 0, _im24.bytes());
		}
		else
			throw exception(std::string("error decompressing sample from AVI stream: ") + decode_icerr(e));
	}

	++_next;
	if (_next_key == _next) {
		_base_key = _next_key;
		_next_key = AVIStreamNextKeyFrame(hstream(), _next_key);
	}
}

byte_image const & reader::frame()
{
	if (!_valid32) {
		assert(_valid24);
		_im32 = _im24;
		_valid32 = true;
	}
	return _im32;
}

small_byte_image const & reader::frame24()
{
	if (!_valid24) {
		assert(_valid32);
		_im24 = _im32;
		_valid24 = true;
	}
	return _im24;
}

} //namespace avi

} //namespace img
